#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>

#define SERV_PORT 8755
#define MAXLINE 32
#define	MAX(a,b) ((a) > (b) ? (a) : (b))
#define error_exit(msg) \
	do { perror(msg); exit(EXIT_FAILURE); } while (0)

void sig_chld(int signo) {
	pid_t pid;
	int stat;
	
	/* 当多个子进程同时终止（多个SIGCHLD信号）时，在一个循环内调用waitpid，以获取所有已终止子进程的状态。
	 * （上述情况如果使用wait，可能会留下僵死进程，而且wait在正运行的子进程尚未终止时必定会阻塞。）
	 * WNOHANG选项告知waitpid在有尚未终止的子进程在运行时不要阻塞。 
	 * waitpid的返回值共有3种情况：
	 *     1.当正常返回的时候，waitpid返回收集到的子进程的进程ID；
	 *     2.如果设置了选项WNOHANG，而调用中waitpid发现没有已退出的子进程可收集，则返回0；
	 *     3.如果调用中出错，则返回-1，这时errno会被设置成相应的值以指示错误所在。 */
	while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child %d terminated\n", pid);
	return;
}

void* Signal(int signo, void (*func)(int)) {
	struct sigaction act, oact;

	/* 设定信号处理函数 */
	act.sa_handler = func; 
	/* 在一个信号处理函数运行期间，正被递交的信号是阻塞的。将sa_mask置为空集，意味着除了被捕获的信号外，没有额外的信号被阻塞。*/
	sigemptyset(&act.sa_mask); 
	/* SIGALRM通常在I/O操作超时时产生，这种情况不希望系统调用被重启. 
	 * 对于默认不自动重启被中断的系统调用的系统，如果设置了SA_RESTART标志，则内核自动重启被信号中断的系统调用. 
	 * 对于默认自动重启被中断的系统调用的系统，如果设置了SA_INTERRUPT标志，则该系统调用被某个信号中断后不重启. */	
	act.sa_flags = 0;
#ifdef SA_INTERRUPT	
	if (signo == SIGALRM) act.sa_flags |= SA_INTERRUPT;
#endif
#ifdef SA_RESTART 
	if (signo != SIGALRM) act.sa_flags |= SA_RESTART; 
#endif
	if (sigaction(signo, &act, &oact) < 0) 
		return SIG_ERR;
	return oact.sa_handler; // 返回信号的旧行为
}		
		
ssize_t writen(int fd, const void *vptr, size_t n) {
	size_t		nleft;
	ssize_t		nwritten;
	const char	*ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0; /* and call write() again */
			else
				return(-1);	
		}

		nleft -= nwritten;
		ptr   += nwritten;
	}
	return n;
}		
		
void doit(int sockfd) {
	ssize_t		n;
	char		buf[MAXLINE];

	/* read从套接字读入数据，如果客户关闭连接，将导致read函数返回0 */
again:
	while ((n = read(sockfd, buf, MAXLINE)) > 0) 
		writen(sockfd, buf, n); // 保证写n个字节，否则失败

	if (n < 0 && errno == EINTR)
		goto again;
	else if (n < 0)
		error_exit("read");
}

#if 0
static void* thread_func(void *arg) {
	int connfd;

	connfd = *((int *)arg);
	free(arg);

	pthread_detach(pthread_self());
	doit(connfd);	
	close(connfd);
	return(NULL);
}
#endif

int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp) {
	int listenfd, n;
	const int on = 1;
	struct addrinfo hints, *res, *ressave;

	bzero(&hints, sizeof(struct addrinfo));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if ((n = getaddrinfo(host, serv, &hints, &res)) != 0) {
		printf("tcp_listen error for %s, %s: %s", host, serv, gai_strerror(n));		
		exit(0);
	}
	ressave = res;

	do {
		listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
		if (listenfd < 0)
			continue; // error, try next one 

		setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
		if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
			break; // success 

		close(listenfd); // bind error, close and try next one 
	} while ((res = res->ai_next) != NULL);
	if (res == NULL) { // errno from final socket() or bind() 
		printf("tcp_listen error for %s, %s", host, serv);
		exit(0);
	}

	listen(listenfd, 5);

	if (addrlenp)
		*addrlenp = res->ai_addrlen;

	freeaddrinfo(ressave);

	return(listenfd);
}

#if 0
int main(int argc, char **argv) {
	int listenfd, *iptr;
	pthread_t tid;
	socklen_t addrlen, len;
	struct sockaddr *cliaddr;

	if (argc == 2)
		listenfd = tcp_listen(NULL, argv[1], &addrlen);
	else if (argc == 3)
		listenfd = tcp_listen(argv[1], argv[2], &addrlen);
	else
		error_exit("usage: tcpserv [ <host> ] <service or port>");

	cliaddr = malloc(addrlen);

	for ( ; ; ) {
		len = addrlen;
		iptr = malloc(sizeof(int));
		*iptr = accept(listenfd, cliaddr, &len);
		pthread_create(&tid, NULL, &thread_func, iptr);
	}
}
#endif

#if 0
int main(int argc, char **argv) {
	int					listenfd, connfd;
	pid_t				childpid;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;
	char buff[MAXLINE];	

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);
	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	listen(listenfd, 5);

	/* 当fork子进程时，必须捕获SIGCHLD信号 */
	Signal(SIGCHLD, sig_chld);
	
	for ( ; ; ) {
		clilen = sizeof(cliaddr);
		if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
			if (errno == EINTR) // 当捕获信号时，必须处理EINTR错误（被信号中断的系统调用）
				continue;
			else if (errno == ECONNABORTED) // 当accept返回前连接被客户终止(RST)，POSIX指出返回的error必须是ECONNABORTED（software caused connection abort）
				continue;
			else
				error_exit("accept");
		}
		printf("connection from %s, port %d\n", 
			inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), 
			ntohs(cliaddr.sin_port));	
		
		/* socket成功返回后，listenfd的引用计数为1; 
		 * accept成功返回后，connfd的引用计数为1。
		 * fork返回后，上述两个描述符在父进程和子进程中共享（被复制），因此两个描述符的引用计数均为2. */
		if (0 == (childpid = fork())) { 
			close(listenfd); // 子进程关闭listenfd，使得listenfd的引用计数减1
			doit(connfd); // 子进程服务客户所需的所有操作
			exit(0); // 子进程终止时会自动关闭所有由内核打开的描述符——close(connfd)此时自动调用
		}
		close(connfd); // 父进程关闭connfd，使得connfd的引用计数减1，然后等待另一个客户连接
	}
}
#endif

#if 1
int main(int argc, char **argv) {
	int                 i, maxi, maxfd;
	int					listenfd, connfd, sockfd;
	int                 nready, client[FD_SETSIZE];
	ssize_t             n;
	fd_set              rset, allset;
	socklen_t			clilen;
	struct sockaddr_in	cliaddr, servaddr;
	char                buf[MAXLINE];	

	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);
	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	listen(listenfd, 5);

	maxfd = listenfd;
	maxi = -1;
	for (i = 0; i < FD_SETSIZE; i++) 
		client[i] = -1;
	FD_ZERO(&allset); // 将allset中的所有项置为0
	FD_SET(listenfd, &allset); // allset中唯一的非0项是listenfd的项
	
	for ( ; ; ) {
		/* select等待allset中描述符可读（建立连接、数据/FIN/RST到达） */
		rset = allset;
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		/* 当listenfd监听描述符可读时，接受一个客户的连接。
		 * 客户连接成功后，服务器保存新的已连接描述符connfd，并把它添加到allset中。 */
		if (FD_ISSET(listenfd, &rset)) { // 当客户与服务器建立连接时，监听套接字变为可读
			clilen = sizeof(cliaddr);
			if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
				if (errno == ECONNABORTED) // 当accept返回前连接被客户终止(RST)，POSIX指出返回的error必须是ECONNABORTED（software caused connection abort）
					continue;
				else
					error_exit("accept");
			}
			printf("connection from %s, port %d\n", 
				inet_ntop(AF_INET, &cliaddr.sin_addr, buf, sizeof(buf)), 
				ntohs(cliaddr.sin_port));
				
			for (i = 0; i < FD_SETSIZE; i++) {
				if (client[i] < 0) {
					client[i] = connfd; // 保存每个新的已连接描述符
					break;
				}
			}
			if (i == FD_SETSIZE) 
				error_exit("too many clients");
			
			FD_SET(connfd, &allset); // connfd添加到allset中
			if (connfd > maxfd) maxfd = connfd; // 更新select参数1——最大描述符
			if (i > maxi) maxi = i; // 更新client[FD_SETSIZE]中保存的已连接描述符最大索引
			if (--nready <= 0) 
				continue;
		}
		
		/* 当已连接描述符（client[maxi]中的一个或多个）可读时，处理客户的连接请求。 */
		for (i = 0; i <= maxi; i++) {
			if ((sockfd = client[i]) < 0) 
				continue;
			if (FD_ISSET(sockfd, &rset)) {
				if ((n = read(sockfd, buf, MAXLINE)) == 0) { // 如果客户终止了连接，read这个套接字将返回0
					close(sockfd); // 关闭该连接描述符
					FD_CLR(sockfd, &allset); // 该连接描述符被移除出allset
					client[i] = -1; // 更新client[i]中该连接描述符值为初始值-1
				}
				else {
					writen(sockfd, buf, n);	
				}

				if (--nready <= 0) // 限定只处理已就绪的描述符
					break;
			}
		}
	}
	return 0;
}
#endif

#if 0
int main(int argc, char **argv) {
	int                 listenfd, connfd;
	pid_t               childpid;
	socklen_t           clilen;
	struct sockaddr_in	cliaddr, servaddr;
	char                buff[MAXLINE];	
	int                 udpfd, nready, maxfdp1;
	char                mesg[MAXLINE];
	fd_set              rset;
	ssize_t             n;
	const int           on = 1;
	
	/* TCP服务器 */
	listenfd = socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family      = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port        = htons(SERV_PORT);
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); // SO_REUSEADDR选项允许重用本地地址（以防该端口上已有连接存在）
	bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	listen(listenfd, 5);

	/* UDP服务器 */
	udpfd = socket(AF_INET, SOCK_DGRAM, 0);
	
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT); // TCP端口与UDP端口相互独立
	bind(udpfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
	
	/* 捕获SIGCHLD信号（当fork子进程时必须） */
	Signal(SIGCHLD, sig_chld);
	
	/* 准备调用select */
	FD_ZERO(&rset);
	maxfdp1 = MAX(listenfd, udpfd) + 1;	
	for ( ; ; ) {
		/* 调用select */
		FD_SET(listenfd, &rset);
		FD_SET(udpfd, &rset);
		if ((nready = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
			if (errno == EINTR)
				continue;
			else
				error_exit("select");
		}	
		
		/* 处理新的TCP连接 */		
		if (FD_ISSET(listenfd, &rset)) {
			clilen = sizeof(cliaddr);
			if ((connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen)) < 0) {
				if (errno == EINTR) continue; // 被信号中断的系统调用
				else if (errno == ECONNABORTED) continue; // 当accept返回前连接被客户终止(RST)
				else error_exit("accept");
			}
			printf("connection from %s, port %d\n", 
				inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), 
				ntohs(cliaddr.sin_port));	
			
			if ((childpid = fork()) == 0) { // 子进程
				close(listenfd);
				doit(connfd); // 子进程处理TCP客户的请求
				exit(0);
			} 
			close(connfd); // 父进程
		}
		
		/* 处理UDP数据报的到达 */	
		if (FD_ISSET(udpfd, &rset)) {
			clilen = sizeof(cliaddr);
			n = recvfrom(udpfd, mesg, MAXLINE, 0, (struct sockaddr *)&cliaddr, &clilen);
			sendto(udpfd, mesg, n, 0, (struct sockaddr *)&cliaddr, clilen);
		}
	}
	
	return 0;
}
#endif



